<?php
ArrowController::importModel("org.arrowplatform.orm.ArrowOrmSqlRouter");
ArrowController::importModel("org.arrowplatform.model.ArrowDataset");
ArrowController::importModel("org.arrowplatform.orm.ArrowOrmJoinCriteria");
ArrowController::importModel("org.arrowplatform.model.IArrowControlable");
ArrowController::importModel("org.arrowplatform.model.ArrowModelAction");


/**
 * @author 3Code Group 
 * @author Pawel Giemza
 * @version 1.1
 * @package Arrow
 * @subpackage Orm
 * @link http://arrowplatform.org/
 * @copyright 2009 3code 
 * @license GNU LGPL
 * 
 * @date 2009-06-01 
*/

/**
 * Basic class for all objects which want to store data in DB.
 */
/**
 * 
 * TODO: AddIArrowFactory inteface to Persistent
 * @author Paweł
 *
 */
abstract class ArrowOrmPersistent implements ArrayAccess, IteratorAggregate, IArrowModel, IArrowModel, ISerializable{

	const CREATE_INDEX = '_Create_';
/** CLASS VARIABLES **/
	public static $ormObjectCache = array();
	
	/**
	 * This array stores next id values for memory object (objects that are created but not saved to db);
	 * Keys: class names, values : id (always less than 0)
	 */
	protected static $memId = array();

	/**
	 * This array stores infomation whether given class has been initialized;
	 * Keys: class names, values : true,false
	 */
	protected static $initialized = array();	
	
	/**
	 * This array stores infomation about nams ofprimary key columns
	 * Keys: class names, values : name of primary key
	 */
	protected static $keyName = array();
	
	
	//TODO remove query mode from Persistent to router (or debug module)   
	protected static $getQuery = false ;
/** ------------------------------------------------------ **/	
/** OBJECT VARIABLES **/
	protected $data = array(); // current values of fields
	protected $db_data = array(); //  values of fields from db
	
	protected $related = array(); // related objects for joins 
	/**
	 * Stores information whether object was modified since last save
	 *
	 * @var bool
	 */
	protected $modified = false;
	/**
	 * 
	 * Value of primary key
	 * @var int
	 */	
	private $key=null;
	/**
	 * 
	 * Name of this object class
	 * @var string
	 */
	protected $class = '';
/** ------------------------------------------------------ **/
/** STATIC METHODS **/
	
	public static function initalize($class){
		self::$memId[$class] = 0;		
		self::$keyName[$class] = ArrowOrmConfig::getKeyName($class);
		self::$ormObjectCache[$class] = array();
		self::$initialized[$class] = true;
		return true;
	}
	
	
	/**
	 * TODO: we need to load objects in dataset and we cannot use default constructors (as they are protected)
	 * 
	 * @param unknown_type $data
	 * @param unknown_type $class
	 * @return unknown
	 */
	public static function datasetConstruct($data,$class){
		return new $class($data);		
	}
	
	/**
	 *  Create new object.
	 *  This function creates new object of class given in $class with db values from $dbValues.
	 *	Notes: Calling this function will not add object to DB. 
	 *  Use 'save' function to store in DB.
	 * 
	 * @param Array $initialValues - values to be stored in object
	 * @param String $class - name of class to instanciate
	 * @return Object	  
	 */	
	public static function create($initialValues, $class = "")
	{
		//ArrowLogger::log("[ArrowOrmPersistent] create(), 'class'=>$class ", ArrowLogger::EL_INFO );
		
		// odzyskanie pełnej ścieżki z modelu
		
		if(!is_array($initialValues))
			throw new ArrowOrmException(array("msg"=>"[ArrowOrmPersistent] Incorrect data delivered to create function", 'hint'=>'Array required here', 'class'=>$class, 'value'=>serialize($initialValues)));
		
		$conf = ArrowOrmConfig::getClassConf($class);
		$fields = ArrowOrmConfig::getFieldList($class);
		foreach ($initialValues as $field=>$val){
			$key_exists = in_array($field, $fields );
			if (!$key_exists)
				throw new ArrowOrmException(array("msg"=>"[ArrowOrmPersistent] Setting temporary variables is not allowed {$class}['{$field}']", "class"=>$class, "field"=>$field));
		}
		if (!isset(self::$memId[$class]))
			self::$memId[$class] = 0;
			
		$pk = $conf['key']['primary'];
		$data = $initialValues;				
		
		if (!isset($initialValues[$pk]))
			$data[$pk] = --self::$memId[$class];//memId is always negative
			
		if (isset($data[self::CREATE_INDEX.$class]) )
			throw new ArrowOrmException(array('msg'=>'[ArrowOrmPersistent] Index '.self::CREATE_INDEX.$class.' is reserved for internal use by ORM'));
		$data[self::CREATE_INDEX.$class] = true;
		$obj = new $class($data);
		
		//$am->turnOn();
		return $obj;
	}
	
	/**
	 * Get object by key.
	 * Returns object of given class and given id.
	 *
	 * @param int $key
	 * @param String $ObjectClass
	 * @return Object
	 */
	public static function getByKeyCached( $key, $class ){
		//ArrowLogger::log("[ArrowOrmPersistent] getByKey() 'key'=> $key, 'class'=>$class", ArrowLogger::EL_INFO );	
		if (!isset(self::$initialized[$class])) self::initalize($class);
		
		if(!isset(self::$ormObjectCache[$class][$key]) || !is_object(self::$ormObjectCache[$class][$key]) ){
			$conf = ArrowOrmConfig::getClassConf($class);
			$criteria = new ArrowOrmCriteria($class);
	
			$criteria->addCondition($conf['key']['primary'], $key);
	
			$list = ArrowOrmSqlRouter::select( $conf['attr']['table'], $conf['attr']['db'], $criteria);	
			$result = array();
			$obj = null;
			foreach ($list as $result){
				$obj = new $class($result);
				//object should be stored in cache already so we will use cache to return it
			}		
		}
		if (isset(self::$ormObjectCache[$class][$key]))
			return self::$ormObjectCache[$class][$key];
		return $obj;	
	}	
	
	/*public static function getByKeyCached($key,$class){
		return self::getByKey($key,$class);
	}*/
	
	public static function getByKey($key, $class ){
			$conf = ArrowOrmConfig::getClassConf($class);
			$criteria = new ArrowOrmCriteria($class);
	
			$criteria->addCondition($conf['key']['primary'], $key);
	
			$list = ArrowOrmSqlRouter::select( $conf['attr']['table'], $conf['attr']['db'], $criteria, self::$getQuery );	
			if(self::$getQuery)
				return $list;
			//$result = array();
			$obj = null;
			foreach ($list as &$result){
				$result = new $class($result);
				//object should be stored in cache already so we will use cache to return it
			}
			if (!empty($list))
				return reset($list);
			return null;	
	}
	
	/**
	 * Returns array of objects of given class (Read from DB).
	 *
	 * If you want to return only part of objects stored in database use ArrowOrmCriteria object.
 	 *
	 * @param ArrowOrmCriteria $criteria	 
	 * @param String $class
	 * @return Array 
	 */
	public static function getByCriteria( $criteria, $class  )
	{		
		
		//get by criteria using joins		
		if ( $criteria instanceof ArrowOrmJoinCriteria ){
			$result = self::getByCriteriaJoin($criteria, $class );
			if(self::$getQuery)
				return $result;
			if(!is_array($result))
				return $result->getBasicObjects();
			return $result;
		}
		//basic get by criteria
		//ArrowLogger::log("[ArrowOrmPersistent] getByCriteria() 'class'=>$class ", ArrowLogger::EL_INFO );			
		$conf = ArrowOrmConfig::getClassConf($class);
		try{
			$criteria->setModel($class);
		}
		catch (ArrowOrmException $e){}
		
		$list = ArrowOrmSqlRouter::select( $conf['attr']['table'], $conf['attr']['db'], $criteria, self::$getQuery );
		if(self::$getQuery) return $list;
		//handling of return sql option (return query instead of processing records)
		$returnResult = array();
		if (!$criteria->isAggregated())
			foreach ($list as $result)
		{
			$obj = new $class($result);
			$returnResult[] = $obj;
		}
		else
			$returnResult = $list;
			//if aggeregates are used, there is no sense in returning objects
		return $returnResult;
	}
	
	
	private static function getByCriteriaJoin( $criteria, $class, $dataset = false ){
			$conf = ArrowOrmConfig::getClassConf($class);

			foreach ($criteria as $crit){
				$model = $crit->getModel();
				ArrowController::importModel($model);
			}	
			
			$list = ArrowOrmSqlRouter::join($conf['attr']['db'], $criteria, self::$getQuery);
			if(self::$getQuery)
				return $list;
				
				
			//convert rows into arrays (base for objects)
			$objectData = array();
			$base_ids = array();
			//translate column names.
			$column_names = array();
			if (!empty($list)){
				$row = reset($list);
				foreach ($row as $field=>$val){
					//get inner class if exist
						$innerclass ='';
					if (strpos($field,'[') !==false && strpos($field,']')!==false ){
						$innerclass = substr($field, strpos($field,'['), strpos($field,']')-strpos($field,'[')+1);
						$field = str_replace($innerclass,'',$field);
					}	 
		
					//$field = explode('::',$field);
					$field = explode(':',$field);
					$column_names[] = array($field[0].$innerclass,$field[1]);
				}
			}	
			foreach ($list as $row){
				$rowData = array();
				$counter = 0;
				foreach ($row as $field=>$val){
					$rowData[$column_names[$counter][0]][$column_names[$counter][1]] = $val;
					$counter++;
				}
				$pk = ArrowOrmConfig::getKeyName($class);
				if (!$criteria->findCriteria($class)->isAggregated()){
					$base_id = $rowData[$class][$pk];
					$base_ids[$base_id] = $base_id;
				}
				$objectData[] = $rowData;
			}

			//aggregated so just return
			if ($criteria->findCriteria($class)->isAggregated()  ){ //&& !$dataset
				if(empty($objectData)) return array();
				return $objectData;
			}
			$dataset = new ArrowDataset($objectData, $criteria->getAvailableRelations());
			return $dataset;
	}
	//Get data from DB without changing it into objects
	public static function getRawData( $criteria, $class ){
		//get by criteria using joins		
		if ( $criteria instanceof ArrowOrmJoinCriteria ){
			$conf = ArrowOrmConfig::getClassConf($class);
			foreach ($criteria as $crit){
				$model = $crit->getModel();
				ArrowController::importModel($model);
			}	
			$list = ArrowOrmSqlRouter::join($conf['attr']['db'], $criteria);
			return $list;
		}
		else{
			//basic get by criteria
			$conf = ArrowOrmConfig::getClassConf($class);
			try{
				$criteria->setModel($class);
			}
			catch (ArrowOrmException $e){}
			$list = ArrowOrmSqlRouter::select( $conf['attr']['table'], $conf['attr']['db'], $criteria);
			return $list;
		}
		return array();
	}
	
	public static function getDataSetByCriteria( $criteria, $class ){
		return self::getByCriteriaJoin($criteria, $class, true );
	}
/** ------------------------------------------------------ **/	
	/**
	 * Default constructor. 
	 * Only for internal use. In order to create new object use create function.
	 * @param Array $initialValues - values to be stored in object
	 * @return Object 	  
	 */
	protected function __construct($initialValues = array())
	{
		$this->class = get_class($this);
		
		if (!isset(self::$initialized[$this->class])) self::initalize($this->class);
		
		if ( isset($initialValues[self::CREATE_INDEX.$this->class]) ){ // create new object
			unset($initialValues[self::CREATE_INDEX.$this->class]);
			foreach ($initialValues as $key=>$val)
					$this->data[$key]=$val;
			$this->modified = true;
		}else{ //load data from db

			//this data was delivered from DB it is assumed to be correct and is not checked
			foreach ($initialValues as $key => $value){
				$this->db_data[$key] = $value;
				$this->data[$key] = &$this->db_data[$key];
			}	
			
			$this->key = &$this->data[self::$keyName[$this->class]];
			$this->modified = false;
			self::$ormObjectCache[$this->class][$this->key] = $this;
		}
	}
	
	/**
	 * Returns array containing all data stored in object. This functions has two modes. 
	 * If $extendedInfo is set to false (default) only class name and object id will be returned, 
	 * in other case also values stored inside object will be returned
 	 *
	 * @param bool $extendedInfo
	 * @return Array 
	 */	
	public function toString($extendedInfo=false){
		$ret = array();
		$ret['class'] = $this->class;
		$pk = self::$keyName[$class];	
		$ret[$pk] = $this->key;	
		if ($extendedInfo){
			foreach ($this as $key =>$value)
				$ret[$key] = $value;
		}
		return $ret;	
	}		
	
	/**
	 * Prepares data to be stored in DB.
	 * @return Array
	 */
	 protected function prepareData(){
	 	//ArrowLogger::log("[ArrowOrmPersistent] prepareData()", ArrowLogger::EL_INFO );
		$class = $this->class;
		$conf = ArrowOrmConfig::getClassConf($class);
		$fields = $conf['field'];
		$data = array();
		foreach ($fields as $val){
			$field = $val['name'];
			$exists = $this->valueExists($field);
			$dbExists = $this->dbValueExists($field);
			$db = $this->getDbValue($field);
			$cur = null;
			if ($exists){
				if (array_key_exists($field,$this->data) )
					$cur = $this->data[$field];
				//nie używać getValue do zapisu
			}	
			// $val['attr']['required']
			if ( $exists && ( ($db !== $cur) || (!$dbExists) ) ) //copy only existing/required fields (or modified fields)
				$data[$field] = $cur;
		}
		return $data;
	}	
	
	/**
	 * Saves object into DB. 
	 * If object already exists in DB it will be updated in other case it will be added.
 	 *
	 * @return void
	 */
	public function save(){		
		$conf = ArrowOrmConfig::getClassConf($this->class);
		$pk = $conf['key']['primary'];
		if ($this->modified == true){	
			$data = $this->prepareData();
			if ($this->key !== null ){ // object already in DB
				$criteria = new ArrowOrmCriteria($this->class);
				$criteria->addCondition($conf['key']['primary'], $this->getDbValue($pk) );
				ArrowOrmSqlRouter::update($conf['attr']['table'], $conf['attr']['db'], $data, $criteria);
				$this->updateDbValues();
			}
			else{ // new object to be added to DB
				if (isset($conf['field'][$pk]['autoIncrement']) && $conf['field'][$pk]['autoIncrement']=='true' && $data[$pk] < 0){
					unset($data[$pk]);
					unset($this->data[$pk]);
				}
				$fields = $conf['field'];
				foreach ($fields as $val){
					$key = $val['name'];
					if (isset($val['required']) && !( array_key_exists('default',$val) || array_key_exists('autoIncrement',$val)  || array_key_exists($key,$this->data) ) )
						throw new ArrowOrmException(array('msg'=>'[ArrowOrmPersistent] Required value '.$key.' not deliverd to create function.', 'field'=>$key));
				}
				$id = ArrowOrmSqlRouter::insert( $conf['attr']['table'], $conf['attr']['db'], $data);
				$this->key = $id;
				$dbState = Persistent::getByKeyCached($id, $this->class); //deliver to object default values set by DB (required)
				$this['id'] = $id;
				$this->updateDbValues(); //synchronize object
				foreach ($fields as $key=>$val){
					//jesli dbState ne jest obiektem sprawdz błąd pk
					if (!$this->valueExists($key) && is_object($dbState)) // only copy values set to default by DB
						$this->setValue($key,$dbState->getDbValue($key));
				}		
				//Calling update here is required in order to update value of PK for newly created objects		
				$this->updateDbValues(); //synchronize object
			}
			$this->modified = false;	
			if (self::isCached($this->class,$this->getPKey()))
				self::$ormObjectCache[$this->class][$this->getPKey()] = $this;
		}	
	}

	/**
	 * Removes object from DB. 
	 *
	 * Note: This function does not remove any related objects.
 	 *
	 * @return void
	 */
	public function delete(){	
		//ArrowLogger::log("[ArrowOrmPersistent] delete()", ArrowLogger::EL_INFO );
		$class = $this->class;
		$conf = ArrowOrmConfig::getClassConf($class);
    	$pk = $conf['key']['primary'];
    	//only delete if object exists in DB
    	if ( $this->getDbValue($pk)){ 
    		$criteria = new ArrowOrmCriteria(get_class($this));
    		$criteria->addCondition($pk, $this->getDbValue($pk) );
    		$result = ArrowOrmSqlRouter::delete($conf['attr']['table'], $conf['attr']['db'], $criteria);
			return $result;			
			if (self::isCached($class,$this->getPKey()))
			unset(self::$ormObjectCache[$class][$this->getPKey()]);
    	}
	}
	

	/**
	 * Sets values of several properties in object
	 *
	 * @param array $values - array of key, value pairs to be set.
	 * 
	 */
	public function setValues( $values , $tmp = false ){
		if (is_array($values)){
			foreach ($values as $key => $value){
				$this->setValue($key, $value, $tmp);
			}
		}
		else
			throw new ArrowOrmException(array('msg'=>'[ArrowOrmPersistent] Incorrect values delivered to setValues function'));
	}
	
	public function valueExists($field){
		if (array_key_exists($field, $this->data))
			return true;
		return false;	
	}
	
	public function dbValueExists($field){
		if (array_key_exists($field, $this->db_data))
			return true;
		return false;	
	}
	
	public function unsetValue($field){
		if (array_key_exists($field, $this->data))
			unset($this->data[$field]);
			
		if (array_key_exists($field, $this->db_data))
			unset($this->db_data[$field]);
		return true;	
	}
	
	public function getValue($field){
		if (array_key_exists($field, $this->data))
			return $this->data[$field];
		else{
			$fields = $this->getFieldnames();
			if (in_array($field, $fields))
				throw new ArrowException(array("msg"=>"[ArrowOrmPersistent] Trying to return property which has not been delivered from DB {$this->class}:{$field} ", "field"=>$field, "class"=>$this->class) );
			else
				throw new ArrowException(array("msg"=>"[ArrowOrmPersistent] Trying to return property which has not been defined for this object {$this->class}:{$field} ", "field"=>$field, "class"=>$this->class, 'data' => $this->data ) );
		}
	}
	
	/**
	 * Value setter.
	 * 
	 * Note:: this data may be incorrect
	 * ArrowOrmPersistent divides fields into two types:
	 * - persistent - fields which will be stored into DB (defined in correct db_schema XML file)
	 * - dynamic - fields which can be used in script but will not be stored in DB.
	 * 
	 * By default any attempt to set a temporary field will cause ArrowOrmException. This 
	 * is used to warn programmer that temporary field is being set. To correctly set such field 
	 * third argument setValue method must be set to true. This means that temporary fields can not 
	 * be ,by default, set with use of ArrayAccess interface.
	 *  
	 */
	public function setValue($field, $value, $tmp=false){
		//TODO Sprawdzic czemu potrzebny jest isset
		//to już nie powinno być potrzebne bo mamy inicjalizacje w konstruktorze
		//if(isset(self::$keyName[$this->class]) && $field == self::$keyName[$this->class])
		//	$this->key = $value;
		
		if ( !$tmp && !in_array($field, ArrowOrmConfig::getFieldList($this->class) ) )
			throw new ArrowOrmException(array("msg"=>"[ArrowOrmPersistent] Setting temporary variables is not allowed {$this->class}['{$field}']", "class"=>$this->class, "field"=>$field));
			
		$this->modified = true;
		
		if($value === 'NULL'){
			$value = null;
		}
		$this->data[$field] = &$value;
	}
	
	/*
	 * Updates DB values stored in object so that they reflect state of DB.
	 * This is also required to ensure that object will not be saved several times (if save is called more than once in one script)
	 */
	final private function updateDbValues(){
		foreach ($this->data as $key=> &$val) 		
			$this->db_data[$key] = &$val;
	}
	/* niwużywane bo zrzygnowaliśmy z pluginów
	public function getBaseValue($field){
		return $this->getValue($field);
	}

	public function setBaseValue($field,$value){
		return $this->setValue($field,$value);
	}
	*/
	/**
	 * Value setter
	 * 
	 * Sets value of field loaded from DB.
	 * @param $field
	 * @return mixed
	 */
	final protected function setDbValue($field, $value){
		//ArrowLogger::log("[ArrowOrmPersistent] setDbValue() field=>$field value=>$value", ArrowLogger::EL_INFO );
		$this->db_data[$field] = $value;
	}
	
	/**
	 * Value getter
	 * 
	 * Return value of field loaded from DB.
	 * @param $field
	 * @return mixed
	 */
	public function getDbValue($field){
		//ArrowLogger::log("[ArrowOrmPersistent] getDbValue() field=>$field", ArrowLogger::EL_INFO );
		if (array_key_exists($field, $this->db_data) )
			return $this->db_data[$field];
		return null;	
	}

	/**
	 * Return primary key value of object.
	 * 
	 * @return int
	 */		
	public function getKey(){
		return $this->key;
		//ArrowLogger::log("[ArrowOrmPersistent] getKey()", ArrowLogger::EL_INFO );		
	}
	/**
	 * Alias to getKey
	 * 
	 */		
	public function getPKey(){
		return $this->key;
		//ArrowLogger::log("[ArrowOrmPersistent] getPKey()", ArrowLogger::EL_INFO );
	}
	
	/**
	 * Converts data returned from DB (from custom query) into objects of specified class
	 * @param $resource
	 * @param $class
	 * @return Array of objects
	 */
	public static function toObject( $resource, $class ) {
		//ArrowLogger::log("[ArrowOrmPersistent] toObject()", ArrowLogger::EL_INFO );
		$tmp = array();
		while( $line = mysql_fetch_assoc($resource) ){
			$obj = new $class($line);
			$obj->modified = false;
			$tmp[] = $obj;
		}		
		return $tmp;
    
	}	
	//-------------------------------------------------------------------------------	
	/**
	 * Returns related objects ( using cofig with foreign keys )
	 * 
	 * @param $class Related class
	 * @param $criteria Additional criteria for related list
	 * @return Array of objects
	 */
	public function getRelated( $class, $criteria = null ){
		//ArrowLogger::log("[ArrowOrmPersistent] getRelated()", ArrowLogger::EL_INFO );
		if( is_null($criteria) )
			$criteria = new ArrowOrmCriteria();
			
    	$providedConf = ArrowOrmConfig::getClassConf($class);
    	$currentConf  = ArrowOrmConfig::getClassConf(get_class($this));

    	if( isset( $providedConf["key"]["foreign"][get_class($this)] ) ){
    		//one to more connection
    		$connectionColumn = $providedConf["key"]["foreign"][get_class($this)];
    		$criteria->addCondition($connectionColumn, $this->getPKey());
    		$result = call_user_func( array( $class, "getByCriteria" ), $criteria, $class  );
    	}else if( isset($currentConf["key"]["foreign"][$class]) ){
    		//more to one (one to one )
    		$connectionColumn = $currentConf["key"]["foreign"][$class];
    		$result = call_user_func( array( $class, "getByKey" ), $this[$connectionColumn], $class  );
    	}
    	else $result = array();
    	
    	return $result;	
	}

//----------------------------------------------------------------------------------------------------	
	/**
	 * Returns configuration data for $class 
	 * @param $class
	 * @return Array
	 */
	public static function getClassConf($class){
		$conf = ArrowOrmConfig::getClassConf($class);
		return $conf;		
	}

	public function getModel(){
		$conf = self::getClassConf($this->class );
		return $conf["attr"]["classpath"];
	}
	
	public static function getModelFromClass( $class  ){
		$conf = self::getClassConf( $class );
		return $conf["attr"]["classpath"];
	}
	
	/**
	 * Returns field list and their types for $class
	 * @param $class
	 * @return Array
	 */
	public static function getClassFieldnames($class){
		$fields = ArrowOrmConfig::getFieldList($class);
		return $fields;		
	}	
	/**
	 * Returns configuration data for this object
	 * @return Array
	 */
	public function getConf(){
		$class = $this->class;
		return self::getClassConf($class);
	}
	
	/**
	 * Returns field list and their types for this object
	 * @return unknown_type
	 */
	public function getFieldnames(){
		return self::getClassFieldnames($this->class);
	}
	
	public function getCurrentFieldnames(){
		return array_keys($this->data);
	}
	
	public static function getCache($class) {	
		if(isset(self::$ormObjectCache[$class]))
			return self::$ormObjectCache[$class];
		else
			array();
	}
	
	
	public static function isCached($class,$key){
		if(isset(self::$ormObjectCache[$class]) && isset(self::$ormObjectCache[$class][$key]) )
			return true;
		return false;
	}
	
	public function addRelated($data,$class){
		if (is_object($data))
			$key = $data->getKey();
		else
			$key = $data;
		if (!isset($this->related[$class][$key]))
			$this->related[$class][$key] = $data;
	}
	
	public function getRel($class=''){
		if ($class=='')
			return array_merge($this->related);
		elseif (isset($this->related[$class])) 
			return array_merge($this->related[$class]);
		else{
			$add = explode(':',$class);
			if (count($add > 1)){
				$className = array_shift($add);
				$add = implode(':',$add);
				if (isset($this->related[$className])){
					$data = $this->related[$className];
					$ret = array();
					foreach ($data as &$obj)
						$ret = array_merge($ret, $obj->getRel($add));
					return $ret;	
				}
			}
		}
		return array();	
	}

	/*
	 * Returns copy of object, (new object already saveed to DB)
	 */
	public function copy($data_to_change = array()){
		$class = $this->class;
		$keyName = ArrowOrmConfig::getKeyName($class);
		$ignored_fields = array($keyName);
		$data_to_copy = array();
		//generate node data
		foreach ($this as $key=>$val)
			if (!in_array($key,$ignored_fields))
				$data_to_copy[$key] = $val;
		//end of generate node data
		//change data
		foreach ($data_to_change as $key=>$val)
			$data_to_copy[$key] = $val;
		//end change data
		$new_obj = call_user_func(array($class,'create'),$data_to_copy);
		$new_obj->save();
		$new_obj = call_user_func(array($class,'getByKey'), $new_obj->getKey(),$class,true);
		return $new_obj;	
	}
	
	/**
	 * Implementation of IObjectSerialize
	 */
	public function serialize(){
		$fields = $this->getFieldnames();
		$keys = array();
		foreach($fields as $field)
			$keys[] = $field;
			
		$values = array();
		foreach($keys as $key) {
			if (array_key_exists($key,$this->data) ) {
				$values[$key] = $this->getValue($key);
			}
		}
		return $values;
	}

	public function unserialize() { }

	public function getObject(){
		return $this;	
	}

	//--------------------implementation of IteratorAggregate interface---------------
	/**
	 * Return instance of iterator used by ArrowModel.
	 * TODO: Integration with data fields access rights
	 * @return Object
	 */
	public function getIterator(){
		$fieldNames = array();
		foreach ($this->data as $key=>$value)
			$fieldNames[] = $key;
		return new ArrowModelIterator($this, $fieldNames);
	}	
	
	//--------------------implementation of ArrayAccess interface--------------------- 	
	/**
	 * Alias to getValue function. Part of ArrayAccess interface.
 	 *
 	 * @param String $offset
	 * @return Mixed $value
	 */		
	final public function offsetGet($offset){
		//ArrowLogger::log("[ArrowModel] offsetGet','class'=>".get_class($this).", 'offset'=>$offset", ArrowLogger::EL_INFO );
		return $this->getValue($offset);
	}

	/**
	 * Alias to setValue function. Part of ArrayAccess interface.
 	 *
 	 * @param String $offset
	 * @param Mixed $value  	 
	 * @return void
	 */		
	final public function offsetSet($offset,$value){
		//ArrowLogger::log("[ArrowModel] offsetSet','class'=>".get_class($this).", 'offset'=>$offset, 'value'=>$value", ArrowLogger::EL_INFO );
		$this->setValue($offset,$value);	
	}

	/**
	 * Alias to valueExists function. Part of ArrayAccess interface.
 	 *
 	 * @param String $offset 	 
	 * @return bool
	 */		
	final public function offsetExists($offset){		
		//ArrowLogger::log("[ArrowModel] offsetExists','class'=>".get_class($this).", 'offset'=>$offset", ArrowLogger::EL_INFO );
		return $this->valueExists($offset);
	}

	/**
	 * Alias to unsetValue function. Part of ArrayAccess interface.
	 *
 	 * @param String $offset	 
	 * @return void
	 */		
	final public function offsetUnset($offset){
		//ArrowLogger::log("[ArrowModel] offsetUnset','class'=>".get_class($this).", 'offset'=>$offset", ArrowLogger::EL_INFO );
		$this->unsetValue($offset);
	}
	
	/**
	 * Returns object class
	 * @return  string
	 */
	public function getClass() {
		return $this->class ;
	}
	/**
	 * REturns class by given classpath
	 * @param string model classpath
	 * @return string
	 */
	public static function getModelClass( $model ){
    $tmp = explode(".",$model);
    return array_pop($tmp);
	}

	
	public static function turnOnQueryMode() {
		ArrowOrmConfig::setOption('query_mode', true);
		self::$getQuery = true ;
	}
	
	public static function turnOffQueryMode() {
		ArrowOrmConfig::setOption('query_mode', false);
		self::$getQuery = false ;
	}
	
	  /** 
  *
  * this method is designd to display data from arrays of objects (as returned from getByCriteria)
  * May return incorrect information or couse error if used with other data
  */
  public static function dump_object($data,$print=true){
    $ret = '';
    if (is_array($data)){
      foreach ($data as $key=>$val){
        $headers = false;
        if (empty($ret)) $headers = true;
        $ret .= $val->dump(false,$headers);
        if ($headers) $ret = str_replace('</table>','',$ret);
      }
      $ret .= '</table>';
    }
    elseif (is_object($data))
      $ret = $data->dump(false);
    else{
     $ret = "incorrect data";
    }   
    if ($print)
      print_r($ret);
    else
      return $ret;
  }
  
  public function dump($print=true,$independent=true){
    $data = $this->data;
    $ret = '';
    if ($independent){
      //prepare headers
      $ret = '<table border="1"><tr><th>'.implode('</th><th>',array_keys($data)).'</th></tr>';
    }
    //prepare data
    $ret .= '<tr>';
    foreach ($data as $field=>$value){
      $ret .= '<td>'.print_r($value,true).'</td>';
    }
    $ret .= '</tr>';
    if ($independent){
      $ret.='</table>';
    }
    if ($print)
      print_r($ret);
    else
      return $ret;
  }
}	
?>